'''
@File    :   statisticTools.py
@Time    :   2021/07/31 08:47:59
@Author  :   Yangjh
@Version :   0.1
@Site    :   https://yangzh.cn
@Desc    :   为社会科学统计分析提供更为便利的函数。
'''
# 导入必要的包
import pandas as pd
from pandas.core.api import DataFrame
from scipy import stats
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
from statsmodels.stats.multicomp import MultiComparison
from pyreadstat import pyreadstat
# 引入docx包，该包的安装为`pip install python-docx`
from docx import Document
import matplotlib.pyplot as plt
import seaborn as sns
from tabulate import tabulate
from lxml import etree
import latex2mathml.converter
import os

# 指定数据所在绝对路径
DATA_URL = R"G:\Users\yangjh\Desktop\repos\statistic-2022\data"
# 指定输出文档所在绝对路径
OUTPUT_URL = R"G:\Users\yangjh\Desktop\repos\statistic-2022\temp"
# 指定MML2OMML文件（插入latex公式时需要的模版）位置
MML2OMML = R'G:\Users\yangjh\Desktop\repos\statistic-2022\assets\MML2OMML.XSL'
# 指定docx文档位置
DOCX_TEMPLATE = R"G:\Users\yangjh\Desktop\repos\statistic-2022\assets\三线表.docx"


def open_docx(filename: str = ''):
    """
    新建或打开docx文件。

    参数：
    filename：表示文件绝对路径及文件名的字符串。
    返回值：
    doc：Document对象
    """
    # 如果找不到文件，就使用模板文件作为模板。
    if (os.path.exists(filename)) is False:
        doc = Document(DOCX_TEMPLATE)
    # 如果文件已经存在，就使用已有文件
    else:
        doc = Document(filename)
    return doc


def p_result(p: float) -> str:
    """
    根据接收到的p值，给出判断结果字典。

    参数：
    p： 各种检验得到的p值。

    返回值：
    以字典形式保存的判断结果。
    """
    if p >= 0.05:
        return {
            'p': f'p={round(p,3)}>0.05',
            'tex_p': 'p>0.05',
            'conclusion': '接收虚无假设，拒绝研究假设。',
        }
    elif p >= 0.01:
        return {
            'p': f'p={round(p,3)}<0.05',
            'tex_p': 'p<0.05',
            'conclusion': '拒绝虚无假设，接收研究假设。',
        }
    elif p >= 0.001:
        return {
            'p': f'p={round(p,3)}<0.01',
            'tex_p': 'p<0.01',
            'conclusion': '拒绝虚无假设，接收研究假设。',
        }
    else:
        return {
            'p': f'p={round(p,3)}<0.001',
            'tex_p': 'p<0.001',
            'conclusion': '拒绝虚无假设，接收研究假设。',
        }


def set_plt():
    """设置mathplotlib图形常用选项"""
    # 设置字体
    plt.rcParams["font.sans-serif"] = ["SimHei"]
    # 该语句解决图像中的“-”负号的乱码问题
    plt.rcParams["axes.unicode_minus"] = False
    # 设置图形大小 大小的单位为英寸 inch
    plt.rcParams["figure.figsize"] = (6.4, 4.8)
    # 设置图形分辨率，出版物对图片的分辨率要求从150到300不等。
    plt.rcParams["figure.dpi"] = 150


def latex_to_word(latex_input):
    """
    将latex公式转变为word适用的公式

    参数：
    latex_input：表示latex公式的语法字符串

    案例：
    document = Document()

    p = document.add_paragraph()
    word_math = latex_to_word(R"\sum_{i=1}^{10}{\frac{\sigma_{zp,i}}{E_i} kN")
    p._element.append(word_math)
    """
    mathml = latex2mathml.converter.convert(latex_input)
    tree = etree.fromstring(mathml)
    xslt = etree.parse(MML2OMML)
    transform = etree.XSLT(xslt)
    new_dom = transform(tree)
    return new_dom.getroot()


def draw_on_corr(coefficient: int) -> str:
    """根据coefficient的值给出建议内容"""
    result = ''
    if coefficient >= 0.8:
        result = '极强相关'
    elif coefficient >= 0.6:
        result = '强相关'
    elif coefficient >= 0.4:
        result = '中等相关'
    elif coefficient >= 0.2:
        result = '弱相关'
    else:
        result = '极弱相关或不相关'
    return result


def draw_on_r(coefficient: int) -> str:
    """根据coefficient的值给出建议内容"""
    result = ''
    if coefficient >= 0.56:
        result = '极强相关'
    elif coefficient >= 0.25:
        result = '中等'
    elif coefficient >= 0.06:
        result = '低度相关'
    else:
        result = '极弱相关或不相关'
    return result


def draw_on_eta2(coefficient: int) -> str:
    """
    根据coefficient的值给出建议内容
    按照J.Cohen 提出的标准，0.01时为小效应，0.06时为中等效应，而0.14为大效应。
    Ferguson( 2009) 总结的社会科学领域小0.04、中0.25、大0.64三种参数水平所对应的各类效应量
    指标临界参考值比 Cohen( 1992) 提出的标准更为严格。
    """
    result = ''
    if coefficient >= 0.14:
        result = '高度相关'
    elif coefficient >= 0.06:
        result = '中度相关'
    elif coefficient >= 0.01:
        result = '低度相关'
    else:
        result = '极弱相关或不相关'
    return result


def goodmanKruska_tau_y(df, x: str, y: str) -> float:
    '''
    古德曼和克鲁斯卡尔 tau(Goodman and Kruskal's tau measure)
    是一种计算两个定类变量相关程度的系数，值介于0-1之间，具有消除误差比例的意义。
    该方法的特点是在计算系数值时，会包含所有的边缘次数和条件次数，故其敏感性高于Lambda系数。
    如果两个变量是不对称关系，最好选用tau-y来简化两个变量的相关情况。

    参考文献：
    李沛良. (2001). 社会研究的统计应用. 社会科学文献出版社

    参数：

    df：DataFrame
    x: 作为自变量的定类变量
    y：作为因变量的定类变量

    返回值：
    tau-y系数，介于0-1之间
    '''
    # 取得条件次数表
    cft = pd.crosstab(df[y], df[x], margins=True)
    # 取得全部个案数目
    n = cft.at['All', 'All']
    # 初始化变量
    E_1 = E_2 = tau_y = 0

    # 计算E_1
    for i in range(cft.shape[0] - 1):
        F_y = cft['All'][i]
        E_1 += ((n - F_y) * F_y) / n
    # 计算E_2
    for j in range(cft.shape[1] - 1):
        for k in range(cft.shape[0] - 1):
            F_x = cft.iloc[cft.shape[0] - 1, j]
            f = cft.iloc[k, j]
            E_2 += ((F_x - f) * f) / F_x
    # 计算tauy
    tau_y = (E_1 - E_2) / E_1

    return tau_y


def save_txt_to_docx(filename: str = '', text: str = '') -> None:
    """
    功能：

    将字符串信息以段落形式追加到docx文档中。

    参数：

    filename：文件名称，最好以绝对地址给出。

    text：要写入docx文档的文本信息。
    """
    # 打开docx文档
    doc = open_docx(filename)
    # 写入段落
    doc.add_paragraph(text, style="正文段落")
    print(text)
    # 保存docx文档
    doc.save(filename)
    # print(f'写入文件完成，{filename},{text}')


def three_line_table(df: DataFrame,
                     output_dir=OUTPUT_URL,
                     docx_name='',
                     extra_row: list = None,
                     caption: str = '') -> None:
    """
    功能：接收数据框 df，将数据框内容以三线表的形式保存到 docx 文档中。

    参数：

    df: 数据框 DataFrame。

    template_url： 模版文件“三线表.docx”所在绝对路径。

    output_dir： 输出结果所在绝对路径。

    docx_name： 输出结果文件名称，如不指定，则为内存地址 id(df)。

    extra_row：额外增加的行，类型为list，如汇总等信息，默认为 None。

    caption: 表格标题，类型为字符串，默认为空字符。
    """
    tabulate(df)

    # 取得行、列数量
    rows = df.shape[0]
    cols = df.shape[1]

    # 打开文档
    filename = f'{OUTPUT_URL}/{docx_name}.docx'
    doc = open_docx(filename)
    # 增加表格题注
    if caption != '':
        doc.add_paragraph(caption, style='Caption')
    # 增加表格本体
    table = doc.add_table(rows=rows + 1, cols=cols, style='三线表')
    # 设置表格对齐为自动
    table.autofit = True

    # 写入表头
    # print(cols)
    # breakpoint()
    for i, item in zip(range(cols), df.columns):
        table.cell(0, i).text = item

    # 写入数据
    for i in range(rows):
        for j in range(cols):
            # print(i, j, df.iloc[i, j])
            table.cell(i + 1, j).text = str(df.iloc[i, j])

    # 写入额外行信息
    if extra_row:
        # 如果有额外行数据，则新增一行数据
        last_row = table.add_row()
        for i in range(cols):
            # 写入传入的参数内容，只取和当前数据框列数相等的前几个元素。
            last_row.cells[i].text = str(extra_row[i])

    # 保存到本地文件
    if docx_name == '':
        docx_name = f'数据框-{str(id(df))}'
    doc.save(filename)


def norminal_desc(df: DataFrame,
                  col: str = '',
                  report: str = '描述统计分析结果') -> None:
    """
    功能：
    对传入的无序类型变量进行描述统计，返回由分析结果组成的数据框。
    同时，在指定目录输出描述统计三线表和柱状图。

    参数：
    df: DataFrame
    col: DataFrame中需要分析的变量，该变量为定类、定序变量
    report: 分析结果保存文件，会生成CSV文件和docx文件
    """
    # 初始化结果DataFrame
    result = pd.DataFrame()
    # 获得变量的内容
    result[f'{col}'] = df[col].value_counts(sort=False).index
    # 获得变量内容出现的次数
    result['个数'] = df[col].value_counts(sort=False).values
    # 获得各选项占比
    result['百分比'] = df[col].value_counts(sort=False,
                                         normalize=True).values * 100
    # 比例小数点后保留1位
    result['百分比'] = result['百分比'].apply(lambda x: round(x, 1))
    print(tabulate(result))
    print('有效样本数量：', result['个数'].sum(), '\n')
    # 构造汇总行内容
    extra = []
    extra.append('总计')
    extra.append(f'{result["个数"].sum()}')
    extra.append(f'{result["百分比"].sum():.0f}')

    # 生成报告文件名
    docx_name = f'变量-{col}-{report}'
    # 调用制表函数
    three_line_table(result, extra_row=extra, docx_name=docx_name)
    # 生成柱状图
    gen_bar_for_norminal_var(df, col)

    # docx包暂时不支持写入svg
    # # 打开word文档
    # doc = Document(f'{OUTPUT_URL}\{docx_name}.docx')
    # # 写入图片
    # doc.add_picture(bar_img)
    # # 保存docx文档
    # doc.save(f'{OUTPUT_URL}\{docx_name}.docx')
    # 写入其他描述统计信息
    # 打开word文档
    filename = f'{OUTPUT_URL}\{docx_name}.docx'
    text = f"""
    变量“{col}”的众值为“{df[col].describe()["top"]}”，次数为{df[col].describe()["freq"]}。
    """
    save_txt_to_docx(filename, text)


def gen_bar_for_norminal_var(df, col, output_url=OUTPUT_URL) -> str:
    """
    功能：

    为数据框中给定的类别变量生成柱状图并保存到指定目录。以字符串形式返回图形完整路径名称。

    参数：

    df： DataFrame

    col: DataFrame中的变量，应该为类别变量

    output_url： 图片保存路径，默认由全局变量OUTPUT_URL指定
    """

    #
    # 设置图表信息
    #

    # 设置字体
    plt.rcParams["font.sans-serif"] = ["SimHei"]
    # 该语句解决图像中的“-”负号的乱码问题
    plt.rcParams["axes.unicode_minus"] = False
    # 设置图形大小 大小的单位为英寸 inch
    plt.rcParams["figure.figsize"] = (6.4, 4.8)
    # 设置图形分辨率，出版物对图片的分辨率要求从150到300不等。
    plt.rcParams["figure.dpi"] = 150

    # 准备数据

    x = df[f'{col}'].value_counts().index
    y = df[f'{col}'].value_counts(normalize=True).values * 100

    # 绘图
    # 创建图
    fig, ax = plt.subplots()
    # 绘制柱状图
    rects1 = ax.bar(x, y)
    # 设定细节内容
    # 设置x轴变量名称
    ax.set_xlabel(f'{col}')
    # 设置y轴最大值
    ax.set_ylim(ymax=100)
    ax.bar_label(rects1, fmt="%.1f", padding=3)
    # 保存图形
    img_name = f'{output_url}\变量-{col}-柱状图.svg'
    plt.savefig(img_name, format='svg')
    return img_name


def gen_scatter(df, x, y, output_url=OUTPUT_URL) -> str:
    """
    功能：

    为数据框中给定的类别变量生成柱状图并保存到指定目录。以字符串形式返回图形完整路径名称。

    参数：

    df： DataFrame

    col: DataFrame中的变量，应该为类别变量

    output_url： 图片保存路径，默认由全局变量OUTPUT_URL指定
    """
    """进行必要的图形设置"""
    set_plt()

    # 准备数据

    # 绘图
    # 创建图
    fig, ax = plt.subplots()
    # 绘制柱状图
    art1 = ax.scatter(df[x], df[y])
    # 设定细节内容
    # 设置x轴变量名称
    ax.set_xlabel(f'{x}')
    # 设置y轴最大值
    ax.set_ylabel(f'{y}')
    # ax.set_ylim(ymax=100)
    # ax.bar_label(rects1, fmt="%.1f", padding=3)
    # 保存图形
    img_name = f'{output_url}\变量-{x}-{y}-散点图.svg'
    plt.savefig(img_name, format='svg')
    return img_name


def ordinal_desc(df: DataFrame,
                 col: str = '',
                 report: str = '描述统计分析结果') -> None:
    """
    功能：
    对传入的有序类型变量进行描述统计，返回由分析结果组成的数据框。
    同时，在指定目录输出描述统计三线表和柱状图。

    参数：
    df: DataFrame
    col: DataFrame中需要分析的变量，该变量为定类、定序变量
    report: 分析结果保存文件，会生成CSV文件和docx文件
    """
    # 初始化结果DataFrame
    result = pd.DataFrame()
    # 获得变量的内容
    result[f'{col}'] = df[col].value_counts(sort=False).index
    # 获得变量内容出现的次数
    result['个数'] = df[col].value_counts(sort=False).values
    # 获得各选项占比
    result['百分比'] = df[col].value_counts(normalize=True,
                                         sort=False).values * 100
    # 小数点后保留1位
    result['百分比'] = result['百分比'].apply(lambda x: round(x, 2))
    # 累计百分比
    result['累计百分比（%）'] = result['百分比'].values.cumsum()
    print(tabulate(result))
    print('有效样本数量：', result['个数'].sum(), '\n')
    # 构造汇总行内容
    extra = []
    extra.append('总计')
    extra.append(f'{result["个数"].sum()}')
    extra.append(f'{result["百分比"].sum():.0f}')
    extra.append('')
    # 生成报告文件名
    docx_name = f'单变量-{col}-{report}'
    # 调用制表函数
    three_line_table(result, extra_row=extra, docx_name=docx_name)
    # 生成柱状图
    gen_bar_for_norminal_var(df, col)
    # docx包暂时不支持写入svg
    # # 打开word文档
    # doc = Document(f'{OUTPUT_URL}\{docx_name}.docx')
    # # 写入图片
    # doc.add_picture(bar_img)
    # # 保存docx文档
    # doc.save(f'{OUTPUT_URL}\{docx_name}.docx')
    # 写入其他描述统计信息
    # 打开word文档
    filename = f'{OUTPUT_URL}\{docx_name}.docx'
    text = f"""
    变量“{col}”的众值为“{df[col].describe()["top"]}”，次数为{df[col].describe()["freq"]}。
    """
    save_txt_to_docx(filename, text)


def scale_desc(df: DataFrame, col: str = '', report: str = '描述统计分析结果') -> None:
    """
    功能：

    对数值变量进行描述统计，计算并输出众数、中位值、四分位值、平均值以及标准差。

    参数：
    df: DataFrame
    col: DataFrame中需要分析的变量，该变量为定类、定序变量
    report: 分析结果保存文件，生成docx文件
    """
    # 初始化结果DataFrame
    result = pd.DataFrame()
    # 计算指标
    result['众数'] = df[col].mode()
    result['中位值'] = df[col].median()
    temp = df[col].quantile([0.25, 0.5, 0.75])
    result['四分位值'] = temp.iloc[2] - temp.iloc[0]
    result['平均值'] = round(df[col].mean(), 2)
    result['标准差'] = round(df[col].std(), 2)
    # 打印结果
    print(tabulate(result))
    print('有效样本数量：', df[col].count(), '\n')

    # 保存结果到word文档
    extra = []
    docx_name = f'单变量-{col}-{report}'
    # 调用制表函数
    three_line_table(result, extra_row=extra, docx_name=docx_name)

    # 还可以通过绘制直方图对数值变量进行描述


def normial_and_normial(df: DataFrame, x: str, y: str):
    """
    功能：
    计算两个无序类别变量之间的相关系数，并制作条件百分表。

    参数：
    df: 包含自变量和因变量的数据框
    x： 自变量
    y： 因变量
    """
    # 生成条件百分表
    result = pd.crosstab(
        df[y],
        df[x],
        normalize='columns',
        margins=True,
        margins_name='合计',
    )
    print(tabulate(result.round(3), result.columns))

    # 增加列
    result.insert(0, y, result.index)

    # 生成报告文件名
    docx_name = f'类别变量-{x}-{y}-分析结果'
    # 调用制表函数
    three_line_table(result.round(3), docx_name=docx_name)
    # 计算相关系数tay-y
    tau_y = goodmanKruska_tau_y(df, x, y)
    info = f'tau_y值为：{tau_y:.3f}，该值属于{draw_on_corr(tau_y)}。'
    # print(info)
    # 将相关系数的结果写入docx文档
    filename = f'{OUTPUT_URL}\{docx_name}.docx'
    save_txt_to_docx(filename, info)

    # 卡方检验
    chi2, p, dof, ex = stats.chi2_contingency(pd.crosstab(df[y], df[x]))
    print(chi2, p, dof)

    # 写入APA结论模版
    N = min(df[y].dropna().shape[0], df[x].dropna().shape[0])
    apa_template_latex = R'\chi^2(9,N=690)=123.345,p<0.01,古德曼和古鲁斯卡的\tau_y=0.033'
    apa_template_before = R"对某地公众所做的调查发现，受教育程度与年收入有关，受教育程度越高，年收入也越高，"
    apa_template_after = R"。进一步分析发现：初中及以下学历者年收入大多在12999美元以下；高中学历者年收入大多在13000~29999美元之间；大学学历者年收入大多在60000美元以上；研究生学历者半数以上年收入在60000美元以上。"
    # 将APA格式模板写入docx文件
    doc = Document(filename)
    doc.add_paragraph('APA格式撰写无序分类变量相关性分析结果模板', style="三级标题")
    # 写入段落
    paragraph = doc.add_paragraph(style="正文段落")
    paragraph.add_run(apa_template_before)
    paragraph._element.append(latex_to_word(apa_template_latex))
    paragraph.add_run(apa_template_after)
    print(apa_template_before + apa_template_latex + apa_template_after)
    # 将数据套入模板后写入docx文档
    doc.add_paragraph('套用APA格式模板', style="三级标题")
    # 写入段落
    paragraph = doc.add_paragraph(style="正文段落")
    apa_template_before = RF"对…………所做的调查发现，{x}与{y}{'无关' if p>=0.05 else '有关'}，{x}……，{y}也……，"
    paragraph.add_run(apa_template_before)
    apa_template_latex = RF'\chi^2({dof},N={N})={chi2:.3f},{p_result(p)["tex_p"]},古德曼和古鲁斯卡的\tau_y={tau_y:.3f}'
    paragraph._element.append(latex_to_word(apa_template_latex))
    apa_template_after = R"。进一步分析发现：…………"
    paragraph.add_run(apa_template_after)
    print(apa_template_before + apa_template_latex + apa_template_after)
    # 保存docx文档
    doc.save(filename)


def ordinal_and_ordinal(df: DataFrame,
                        x: str,
                        y: str,
                        alternative='two-sided'):
    """
    功能：
    计算两个有序类别变量之间的相关系数，并制作条件百分表。
    TODO： 检验等推论检验结果解读

    参数：
    df: 包含自变量和因变量的数据框
    x： 自变量
    y： 因变量
    alternative: 单侧还是双侧检验，默认为two-sided双侧。{‘two-sided’, ‘less’, ‘greater’}
    """
    # 生成条件百分表
    result = pd.crosstab(df[y],
                         df[x],
                         normalize='columns',
                         margins=True,
                         margins_name='合计')
    print(tabulate(result.round(3), result.columns))

    # 增加列
    result.insert(0, y, result.index)

    # 生成报告文件名
    docx_name = f'类别变量-{x}-{y}-分析结果'
    # 调用制表函数
    three_line_table(result.round(3), docx_name=docx_name)
    # 计算相关系数somers dy 及 p 值
    x1 = df[x].cat.codes
    y1 = df[y].cat.codes
    dy = stats.somersd(x1, y1, alternative=alternative)
    print(dy)
    p = dy.pvalue
    info = f"萨莫司dy值为：{dy.statistic:.3f}，该值属于{draw_on_corr(dy.statistic)}，p值为{p:.3f}，{p_result(p)['conclusion']}"

    # 将相关系数的结果写入docx文档
    filename = f'{OUTPUT_URL}\{docx_name}.docx'
    save_txt_to_docx(filename, info)

    # 写入APA结论模版
    # N = min(df[y].dropna().shape[0], df[x].dropna().shape[0])
    apa_template_latex = R'd_y(22)=0.033,p<0.01'
    apa_template_before = R"对某地公众所做的调查发现，受教育程度与年收入有关，受教育程度越高，年收入也越高，"
    apa_template_after = R"。进一步分析发现：初中及以下学历者年收入大多在12999美元以下；高中学历者年收入大多在13000~29999美元之间；大学学历者年收入大多在60000美元以上；研究生学历者半数以上年收入在60000美元以上。"

    # 将APA格式模板写入docx文件
    doc = Document(filename)
    doc.add_paragraph('APA格式撰写有序分类变量相关性分析结果模板', style="三级标题")
    # 写入段落
    paragraph = doc.add_paragraph(style="正文段落")
    paragraph.add_run(apa_template_before)
    paragraph._element.append(latex_to_word(apa_template_latex))
    paragraph.add_run(apa_template_after)
    print(apa_template_before + apa_template_latex + apa_template_after)

    #  将数据套入模板后写入docx文档
    doc.add_paragraph('套用APA格式模板', style="三级标题")
    # 写入段落
    paragraph = doc.add_paragraph(style="正文段落")
    apa_template_before = RF"对…………所做的调查发现，{x}与{y}{'无关' if p>=0.05 else '有关'}，{x}……，{y}也……，"
    paragraph.add_run(apa_template_before)
    # R'\d_y(22)=0.033,p<0.01'
    apa_template_latex = RF'd_y={dy.statistic:.3f}, {p_result(p)["tex_p"]}'
    paragraph._element.append(latex_to_word(apa_template_latex))
    apa_template_after = R"。进一步分析发现：…………"
    paragraph.add_run(apa_template_after)
    print(apa_template_before + apa_template_latex + apa_template_after)
    # 保存docx文档
    doc.save(filename)


def scale_and_scale(df: DataFrame, x: str, y: str):
    """
    功能：
    计算两个数值变量之间的相关系数，并制作图和结论docx文档。
    TODO： 检验等推论检验结果解读

    参数：
    df: 包含自变量和因变量的数据框
    x： 自变量
    y： 因变量
    """

    # 绘制散点图
    gen_scatter(df, x, y)

    # 生成报告文件名
    docx_name = f'数值变量-{x}-{y}-分析结果'

    # 计算积矩相关系数R
    r, p = stats.pearsonr(df[x], df[y])
    info = f"积矩相关系数r为：{r:.3f}，决定系数r平方为：{r*r:.3f}，相关强度为{draw_on_r(r*r)}。"

    # 将相关系数及检验的结果写入docx文档
    filename = f'{OUTPUT_URL}\{docx_name}.docx'
    save_txt_to_docx(filename, info)


def normial_and_scale(df: DataFrame, x: str, y: str):
    """
    功能：
    计算无序类别变量与数值变量之间的相关系数，并制作图和结论docx文档。

    参数：
    df: 包含自变量和因变量的数据框
    x： 自变量
    y： 因变量
    """
    # 生成报告文件名
    docx_name = f'变量-{x}-{y}-分析结果'
    # 生成分组描述统计表格
    df_desc = pd.DataFrame()
    group = df.groupby(x)
    df_desc['个案数'] = group.size()
    df_desc.insert(loc=0, column=x, value=df_desc.index)
    df_desc['平均值'] = df.groupby(x)[y].mean()
    df_desc['标准差'] = df.groupby(x)[y].std()
    df_desc['最小值'] = df.groupby(x)[y].min()
    df_desc['最大值'] = df.groupby(x)[y].max()
    print(df_desc)
    # 输出单因素方差分析结果到docx文档
    three_line_table(
        df_desc.round(3),
        docx_name=docx_name,
        caption=f'{y}分组描述统计表',
    )
    # 绘制箱型图
    set_plt()
    sns.boxplot(
        x=x,
        y=y,
        data=df,
        color='white',
        linewidth=1,
        width=0.5,
    )
    img_name = f'{OUTPUT_URL}\变量-{x}-{y}-箱型图.svg'
    plt.savefig(img_name, format='svg')

    # 计算相关比率
    model = ols(f'{y} ~ {x}', df).fit()
    eta_2 = model.rsquared
    info = f"""相关比率为：{eta_2:.3f}，按照J.Cohen 提出的标准(0.01时为小效应，0.06时为中等效应，而0.14为大效应)，强度为{draw_on_eta2(eta_2)}。
    """
    # 写入文档
    filename = f'{OUTPUT_URL}\{docx_name}.docx'
    save_txt_to_docx(filename, info)

    # 进行单因素方差分析
    anova_table = anova_lm(model, typ=2)
    anova_table.insert(loc=0, column='变异来源', value=anova_table.index)
    # 输出单因素方差分析结果屏幕
    print(anova_table)
    # 输出单因素方差分析结果到docx文档
    three_line_table(
        anova_table.round(3),
        docx_name=docx_name,
        caption='单因素方差分析摘要表',
    )

    # 查看F检验结果，并给出结论
    p = anova_table["PR(>F)"][0].round(3)
    info = f"{p_result(p)['p']}, {p_result(p)['conclusion']}"
    save_txt_to_docx(filename, info)

    # 如果p<0.05,则进行事后检验，以便知道不同组别之间的差异。
    if p < 0.05:
        mc = MultiComparison(df[y], df[x])
        tukey_result = mc.tukeyhsd(alpha=0.5)
        # 美化输出
        results_as_html = tukey_result.summary().as_html()
        # 将html表格，转为DataFrame
        df_tukey = pd.read_html(results_as_html, header=0)[0]
        # 保存表格到docx
        three_line_table(df_tukey.round(3),
                         docx_name=docx_name,
                         caption='Tukey 事后检验')
        # 写入APA格式的分析结果模板
        doc = Document(filename)
        doc.add_paragraph('APA格式撰写单因素方差分析结果模板', style="三级标题")
        # 写入段落，不够优雅，TODO mardkown格式转化的函数。
        paragraph = doc.add_paragraph(style="正文段落")
        paragraph.add_run("手部稳定性会因睡眠剥夺时间而有差异，")
        paragraph._element.append(latex_to_word('F(3,28)=7.50,p=0.001'))
        paragraph.add_run("，相关系数")
        paragraph._element.append(latex_to_word('\eta^2=0.4455'))
        paragraph.add_run("。使用Tukey法进行事后检验，30小时未睡者（")
        paragraph._element.append(latex_to_word('M=6.25，SD=2.12'))
        paragraph.add_run("）显著比12小时未睡者（")
        paragraph._element.append(latex_to_word('M=3.00，SD=1.51'))
        paragraph.add_run("）及18小时未睡者（")
        paragraph._element.append(latex_to_word('M=3.50，SD=0.93'))
        paragraph.add_run("更不稳定，其他组之间没有显著差异。")
        # 保存docx文档
        doc.save(filename)


if __name__ == '__main__':
    # 构造测试数据
    d = {
        "one": pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"]),
        "two": pd.Series([1.0, 2.0, 3.0, 4.0], index=["a", "b", "c", "d"]),
    }

    df1 = pd.DataFrame(d)

    df2, metadata2 = pyreadstat.read_sav(
        R'G:\Users\yangjh\Desktop\repos\notes\02-job\0201-教学\统计与数据分析\data\国家认同数据清理后.sav',
        apply_value_formats=True,
        formats_as_ordered_category=True)
    df3 = pd.read_csv(
        R'G:\Users\yangjh\Desktop\repos\notes\02-job\0201-教学\统计与数据分析\data\movie-order.csv'
    )
    df4, metadata4 = pyreadstat.read_sav(
        R'G:\Users\yangjh\Desktop\repos\notes\02-job\0201-教学\统计与数据分析\data\score.sav',
        apply_value_formats=True,
        formats_as_ordered_category=False,
    )
    # print(df1)
    # for i in range(df.shape[0]):
    #     for j in range(df.shape[1]):
    #         print(i, j, df.iloc[i, j])
    # extra = ['总计', '100', 1000]
    # print(extra[0])
    # for i in range(len(extra)):
    #     print(str(extra[i]))
    # three_line_table(df, docx_name='测试', extra_row=extra)
    # three_line_table(df, docx_name='测试')
    # norminal_desc(df2, '年级')
    # ordinal_desc(df, '会打多少分')
    # scale_desc(df3, 'average')
    # scale_desc(df2, 'votes')
    # gen_bar_for_norminal_var(df, 'one')
    # print(tabulate(df2))
    # normial_and_normial(df2, x='年级', y='政治面貌')
    ordinal_and_ordinal(df2, x='年级', y='会打多少分')
    # gen_scatter(df3, 'average', 'votes')
    # scale_and_scale(df3.dropna(), 'average', 'votes')
    # normial_and_scale(df4.dropna(), '职业', '英语成绩')
    # print(df2[['年级', '会打多少分']].corr(method='spearman'))
